/*
 *  JMule - Java file sharing client
 *  Copyright (C) 2007-2008 JMule team ( jmule@jmule.org / http://jmule.org )
 *
 *  Any parts of this program derived from other projects, or contributed
 *  by third-party developers are copyrighted by their respective authors.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */
package org.jmule.ui.swing.common;

import java.awt.Color;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.metal.MetalTabbedPaneUI;

/**
 *
 * Created on Sep 7, 2008
 * @author javajox
 * @version $Revision: 1.1 $
 * Last changed by $Author: javajox $ on $Date: 2008/09/07 16:52:15 $
 */

public class CloseableTabbedPane extends JTabbedPane implements MouseListener, MouseMotionListener {

/**
 * The <code>EventListenerList</code>.
 */
private EventListenerList listenerList = null;

/**
 * The viewport of the scrolled tabs.
 */
private JViewport headerViewport = null;

/**
 * The normal closeicon.
 */
private Icon normalCloseIcon = null;

/**
 * The closeicon when the mouse is over.
 */
private Icon hooverCloseIcon = null;

/**
 * The closeicon when the mouse is pressed.
 */
private Icon pressedCloseIcon = null;

/**
 * Creates a new instance of <code>CloseableTabbedPane</code>
 */
public CloseableTabbedPane() {
  super();
  init(SwingUtilities.LEFT);
}

/**
 * Creates a new instance of <code>CloseableTabbedPane</code>
 * @param horizontalTextPosition the horizontal position of the text (e.g.
 * SwingUtilities.TRAILING or SwingUtilities.LEFT)
 */
public CloseableTabbedPane(int horizontalTextPosition) {
  super();
  init(horizontalTextPosition);
}

/**
 * Initializes the <code>CloseableTabbedPane</code>
 * @param horizontalTextPosition the horizontal position of the text (e.g.
 * SwingUtilities.TRAILING or SwingUtilities.LEFT)
 */
private void init(int horizontalTextPosition) {
  listenerList = new EventListenerList();
  addMouseListener(this);
  addMouseMotionListener(this);

  if (getUI() instanceof MetalTabbedPaneUI)
    setUI(new CloseableMetalTabbedPaneUI(horizontalTextPosition));
  else
    setUI(new CloseableTabbedPaneUI(horizontalTextPosition));
}

/**
 * Allows setting own closeicons.
 * @param normal the normal closeicon
 * @param hoover the closeicon when the mouse is over
 * @param pressed the closeicon when the mouse is pressed
 */
public void setCloseIcons(Icon normal, Icon hoover, Icon pressed) {
  normalCloseIcon = normal;
  hooverCloseIcon = hoover;
  pressedCloseIcon = pressed;
}

/**
 * Adds a <code>Component</code> represented by a title and no icon.
 * @param title the title to be displayed in this tab
 * @param component the component to be displayed when this tab is clicked
 */
public void addTab(String title, Component component) {
  addTab(title, component, null);
}

/**
 * Adds a <code>Component</code> represented by a title and an icon.
 * @param title the title to be displayed in this tab
 * @param component the component to be displayed when this tab is clicked
 * @param extraIcon the icon to be displayed in this tab
 */
public void addTab(String title, Component component, Icon extraIcon) {
  boolean doPaintCloseIcon = true;
  try {
    Object prop = null;
    if ((prop = ((JComponent) component).
                  getClientProperty("isClosable")) != null) {
      doPaintCloseIcon = (Boolean) prop;
    }
  } catch (Exception ignored) {/*Could probably be a ClassCastException*/}

  super.addTab(title,
               doPaintCloseIcon ? new CloseTabIcon(extraIcon) : null,
               component);

  if (headerViewport == null) {
    for (Component c : getComponents()) {
      if ("TabbedPane.scrollableViewport".equals(c.getName()))
        headerViewport = (JViewport) c;
    }
  }
}

/**
 * Invoked when the mouse button has been clicked (pressed and released) on
 * a component.
 * @param e the <code>MouseEvent</code>
 */
public void mouseClicked(MouseEvent e) {
  processMouseEvents(e);
}

/**
 * Invoked when the mouse enters a component.
 * @param e the <code>MouseEvent</code>
 */
public void mouseEntered(MouseEvent e) { }

/**
 * Invoked when the mouse exits a component.
 * @param e the <code>MouseEvent</code>
 */
public void mouseExited(MouseEvent e) {
  for (int i=0; i<getTabCount(); i++) {
    CloseTabIcon icon = (CloseTabIcon) getIconAt(i);
    if (icon != null)
      icon.mouseover = false;
  }
  repaint();
}

/**
 * Invoked when a mouse button has been pressed on a component.
 * @param e the <code>MouseEvent</code>
 */
public void mousePressed(MouseEvent e) {
  processMouseEvents(e);
}

/**
 * Invoked when a mouse button has been released on a component.
 * @param e the <code>MouseEvent</code>
 */
public void mouseReleased(MouseEvent e) { }

/**
 * Invoked when a mouse button is pressed on a component and then dragged.
 * <code>MOUSE_DRAGGED</code> events will continue to be delivered to the
 * component where the drag originated until the mouse button is released
 * (regardless of whether the mouse position is within the bounds of the
 * component).<br/>
 * <br/>
 * Due to platform-dependent Drag&Drop implementations,
 * <code>MOUSE_DRAGGED</code> events may not be delivered during a native
 * Drag&amp;Drop operation.
 * @param e the <code>MouseEvent</code>
 */
public void mouseDragged(MouseEvent e) {
  processMouseEvents(e);
}

/**
 * Invoked when the mouse cursor has been moved onto a component but no
 * buttons have been pushed.
 * @param e the <code>MouseEvent</code>
 */
public void mouseMoved(MouseEvent e) {
  processMouseEvents(e);
}

/**
 * Processes all caught <code>MouseEvent</code>s.
 * @param e the <code>MouseEvent</code>
 */
private void processMouseEvents(MouseEvent e) {
  int tabNumber = getUI().tabForCoordinate(this, e.getX(), e.getY());
  if (tabNumber < 0) return;
  CloseTabIcon icon = (CloseTabIcon) getIconAt(tabNumber);
  if (icon != null) {
    Rectangle rect= icon.getBounds();
    Point pos = headerViewport == null ?
                new Point() : headerViewport.getViewPosition();
    Rectangle drawRect = new Rectangle(
      rect.x - pos.x, rect.y - pos.y, rect.width, rect.height);

    if (e.getID() == e.MOUSE_PRESSED) {
      icon.mousepressed = e.getModifiers() == e.BUTTON1_MASK;
      repaint(drawRect);
    } else if (e.getID() == e.MOUSE_MOVED || e.getID() == e.MOUSE_DRAGGED ||
               e.getID() == e.MOUSE_CLICKED) {
      pos.x += e.getX();
      pos.y += e.getY();
      if (rect.contains(pos)) {
        if (e.getID() == e.MOUSE_CLICKED) {
          int selIndex = getSelectedIndex();
          if (fireCloseTab(selIndex)) {
            if (selIndex > 0) {
              // to prevent uncatchable null-pointers
              Rectangle rec = getUI().getTabBounds(this, selIndex - 1);

              MouseEvent event = new MouseEvent((Component) e.getSource(),
                                                e.getID() + 1,
                                                System.currentTimeMillis(),
                                                e.getModifiers(),
                                                rec.x,
                                                rec.y,
                                                e.getClickCount(),
                                                e.isPopupTrigger(),
                                                e.getButton());
              dispatchEvent(event);
            }
            //the tab is being closed
            //removeTabAt(tabNumber);
            remove(selIndex);
          } else {
            icon.mouseover = false;
            icon.mousepressed = false;
            repaint(drawRect);
          }
        } else {
          icon.mouseover = true;
          icon.mousepressed = e.getModifiers() == e.BUTTON1_MASK;
        }
      } else {
        icon.mouseover = false;
      }
      repaint(drawRect);
    }
  }
}

/**
 * Adds an <code>CloseableTabbedPaneListener</code> to the tabbedpane.
 * @param l the <code>CloseableTabbedPaneListener</code> to be added
 */
public void addCloseableTabbedPaneListener(CloseableTabbedPaneListener l) {
  listenerList.add(CloseableTabbedPaneListener.class, l);
}

/**
 * Removes an <code>CloseableTabbedPaneListener</code> from the tabbedpane.
 * @param l the listener to be removed
 */
public void removeCloseableTabbedPaneListener(CloseableTabbedPaneListener l) {
  listenerList.remove(CloseableTabbedPaneListener.class, l);
}

/**
 * Returns an array of all the <code>SearchListener</code>s added to this
 * <code>SearchPane</code> with addSearchListener().
 * @return all of the <code>SearchListener</code>s added or an empty array if
 * no listeners have been added
 */
public CloseableTabbedPaneListener[] getCloseableTabbedPaneListener() {
  return listenerList.getListeners(CloseableTabbedPaneListener.class);
}

/**
 * Notifies all listeners that have registered interest for notification on
 * this event type.
 * @param tabIndexToClose the index of the tab which should be closed
 * @return true if the tab can be closed, false otherwise
 */
protected boolean fireCloseTab(int tabIndexToClose) {
  boolean closeit = true;
  // Guaranteed to return a non-null array
  Object[] listeners = listenerList.getListenerList();
  for (Object i : listeners) {
    if (i instanceof CloseableTabbedPaneListener) {
      if (!((CloseableTabbedPaneListener) i).closeTab(tabIndexToClose)) {
        closeit = false;
        break;
      }
    }
  }
  return closeit;
}

/**
 * The class which generates the 'X' icon for the tabs. The constructor
 * accepts an icon which is extra to the 'X' icon, so you can have tabs
 * like in JBuilder. This value is null if no extra icon is required.
 */
class CloseTabIcon implements Icon {
  /**
   * the x position of the icon
   */
  private int x_pos;

  /**
   * the y position of the icon
   */
  private int y_pos;

  /**
   * the width the icon
   */
  private int width;

  /**
   * the height the icon
   */
  private int height;

  /**
   * the additional fileicon
   */
  private Icon fileIcon;

  /**
   * true whether the mouse is over this icon, false otherwise
   */
  private boolean mouseover = false;

  /**
   * true whether the mouse is pressed on this icon, false otherwise
   */
  private boolean mousepressed = false;

  /**
   * Creates a new instance of <code>CloseTabIcon</code>
   * @param fileIcon the additional fileicon, if there is one set
   */
  public CloseTabIcon(Icon fileIcon) {
    this.fileIcon = fileIcon;
    width  = 16;
    height = 16;
  }

  /**
   * Draw the icon at the specified location. Icon implementations may use the
   * Component argument to get properties useful for painting, e.g. the
   * foreground or background color.
   * @param c the component which the icon belongs to
   * @param g the graphic object to draw on
   * @param x the upper left point of the icon in the x direction
   * @param y the upper left point of the icon in the y direction
   */
  public void paintIcon(Component c, Graphics g, int x, int y) {
    boolean doPaintCloseIcon = true;
    try {
      // JComponent.putClientProperty("isClosable", new Boolean(false));
      JTabbedPane tabbedpane = (JTabbedPane) c;
      int tabNumber = tabbedpane.getUI().tabForCoordinate(tabbedpane, x, y);
      JComponent curPanel = (JComponent) tabbedpane.getComponentAt(tabNumber);
      Object prop = null;
      if ((prop = curPanel.getClientProperty("isClosable")) != null) {
        doPaintCloseIcon = (Boolean) prop;
      }
    } catch (Exception ignored) {/*Could probably be a ClassCastException*/}
    if (doPaintCloseIcon) {
      x_pos = x;
      y_pos = y;
      int y_p = y + 1;

      if (normalCloseIcon != null && !mouseover) {
        normalCloseIcon.paintIcon(c, g, x, y_p);
      } else if (hooverCloseIcon != null && mouseover && !mousepressed) {
        hooverCloseIcon.paintIcon(c, g, x, y_p);
      } else if (pressedCloseIcon != null && mousepressed) {
        pressedCloseIcon.paintIcon(c, g, x, y_p);
      } else {
        y_p++;

        Color col = g.getColor();

        if (mousepressed && mouseover) {
          g.setColor(Color.WHITE);
          g.fillRect(x+1, y_p, 12, 13);
        }

        g.setColor(Color.black);
        g.drawLine(x+1, y_p, x+12, y_p);
        g.drawLine(x+1, y_p+13, x+12, y_p+13);
        g.drawLine(x, y_p+1, x, y_p+12);
        g.drawLine(x+13, y_p+1, x+13, y_p+12);
        g.drawLine(x+3, y_p+3, x+10, y_p+10);
        if (mouseover)
          g.setColor(Color.GRAY);
        g.drawLine(x+3, y_p+4, x+9, y_p+10);
        g.drawLine(x+4, y_p+3, x+10, y_p+9);
        g.drawLine(x+10, y_p+3, x+3, y_p+10);
        g.drawLine(x+10, y_p+4, x+4, y_p+10);
        g.drawLine(x+9, y_p+3, x+3, y_p+9);
        g.setColor(col);
        if (fileIcon != null) {
          fileIcon.paintIcon(c, g, x+width, y_p);
        }
      }
    }
  }

  /**
   * Returns the icon's width.
   * @return an int specifying the fixed width of the icon.
   */
  public int getIconWidth() {
    return width + (fileIcon != null ? fileIcon.getIconWidth() : 0);
  }

  /**
   * Returns the icon's height.
   * @return an int specifying the fixed height of the icon.
   */
  public int getIconHeight() {
    return height;
  }

  /**
   * Gets the bounds of this icon in the form of a <code>Rectangle<code>
   * object. The bounds specify this icon's width, height, and location
   * relative to its parent.
   * @return a rectangle indicating this icon's bounds
   */
  public Rectangle getBounds() {
    return new Rectangle(x_pos, y_pos, width, height);
  }
}

/**
 * A specific <code>BasicTabbedPaneUI</code>.
 */
class CloseableTabbedPaneUI extends BasicTabbedPaneUI {

 /**
  * the horizontal position of the text
  */
  private int horizontalTextPosition = SwingUtilities.LEFT;

  /**
   * Creates a new instance of <code>CloseableTabbedPaneUI</code>
   */
  public CloseableTabbedPaneUI() {
  }

  /**
   * Creates a new instance of <code>CloseableTabbedPaneUI</code>
   * @param horizontalTextPosition the horizontal position of the text (e.g.
   * SwingUtilities.TRAILING or SwingUtilities.LEFT)
   */
  public CloseableTabbedPaneUI(int horizontalTextPosition) {
    this.horizontalTextPosition = horizontalTextPosition;
  }

  /**
   * Layouts the label
   * @param tabPlacement the placement of the tabs
   * @param metrics the font metrics
   * @param tabIndex the index of the tab
   * @param title the title of the tab
   * @param icon the icon of the tab
   * @param tabRect the tab boundaries
   * @param iconRect the icon boundaries
   * @param textRect the text boundaries
   * @param isSelected true whether the tab is selected, false otherwise
   */
  protected void layoutLabel(int tabPlacement, FontMetrics metrics,
                             int tabIndex, String title, Icon icon,
                             Rectangle tabRect, Rectangle iconRect,
                             Rectangle textRect, boolean isSelected) {

    textRect.x = textRect.y = iconRect.x = iconRect.y = 0;

    javax.swing.text.View v = getTextViewForTab(tabIndex);
    if (v != null) {
      tabPane.putClientProperty("html", v);
    }

    SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
                                       metrics, title, icon,
                                       SwingUtilities.CENTER,
                                       SwingUtilities.CENTER,
                                       SwingUtilities.CENTER,
                                       //SwingUtilities.TRAILING,
                                       horizontalTextPosition,
                                       tabRect,
                                       iconRect,
                                       textRect,
                                       textIconGap + 2);

    tabPane.putClientProperty("html", null);
    
    int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
    int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
    iconRect.x += xNudge;
    iconRect.y += yNudge;
    textRect.x += xNudge;
    textRect.y += yNudge;
  }
}

/**
 * A specific <code>MetalTabbedPaneUI</code>.
 */
class CloseableMetalTabbedPaneUI extends MetalTabbedPaneUI {

 /**
  * the horizontal position of the text
  */
  private int horizontalTextPosition = SwingUtilities.LEFT;

  /**
   * Creates a new instance of <code>CloseableMetalTabbedPaneUI</code>
   */
  public CloseableMetalTabbedPaneUI() {
  }

  /**
   * Creates a new instance of <code>CloseableMetalTabbedPaneUI</code>
   * @param horizontalTextPosition the horizontal position of the text (e.g.
   * SwingUtilities.TRAILING or SwingUtilities.LEFT)
   */
  public CloseableMetalTabbedPaneUI(int horizontalTextPosition) {
    this.horizontalTextPosition = horizontalTextPosition;
  }

  /**
   * Layouts the label
   * @param tabPlacement the placement of the tabs
   * @param metrics the font metrics
   * @param tabIndex the index of the tab
   * @param title the title of the tab
   * @param icon the icon of the tab
   * @param tabRect the tab boundaries
   * @param iconRect the icon boundaries
   * @param textRect the text boundaries
   * @param isSelected true whether the tab is selected, false otherwise
   */
  protected void layoutLabel(int tabPlacement, FontMetrics metrics,
                             int tabIndex, String title, Icon icon,
                             Rectangle tabRect, Rectangle iconRect,
                             Rectangle textRect, boolean isSelected) {

    textRect.x = textRect.y = iconRect.x = iconRect.y = 0;

    javax.swing.text.View v = getTextViewForTab(tabIndex);
    if (v != null) {
      tabPane.putClientProperty("html", v);
    }

    SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
                                       metrics, title, icon,
                                       SwingUtilities.CENTER,
                                       SwingUtilities.CENTER,
                                       SwingUtilities.CENTER,
                                       //SwingUtilities.TRAILING,
                                       horizontalTextPosition,
                                       tabRect,
                                       iconRect,
                                       textRect,
                                       textIconGap + 2);

    tabPane.putClientProperty("html", null);
    
    int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
    int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
    iconRect.x += xNudge;
    iconRect.y += yNudge;
    textRect.x += xNudge;
    textRect.y += yNudge;
  }
}

}

